Skip to content

Conversation

@ymc9
Copy link
Member

@ymc9 ymc9 commented Aug 18, 2025

Summary by CodeRabbit

  • New Features
    • CLI now supports plugin-based generation with built-in TypeScript and Prisma plugins, customizable outputs, and custom plugins.
    • Runtime adds provider-aware distinct, centralized filter/sort/pagination handling, and maps custom types to JSONB.
  • Refactor
    • Unified query-building flow; stricter input validation; aligned operation names for plugin hooks.
  • Tests
    • Added plugin integration and provider-specific runtime tests; removed tests for deprecated options.
  • Chores
    • Version bump to 3.0.0-alpha.27; dependency updates; tsconfig cleanups; sample scripts use “zen”; debug config tweaks.
  • Documentation
    • Updated TODO progress and tasks.

ymc9 and others added 6 commits August 15, 2025 21:35
* fix: use z.strictObject consistently

* use nominal operator for onQuery plugin callback
* fix: disallow distinct for sqlite

* update

* update
* feat: cli plugin support

* update

* update

* more fixes
Copilot AI review requested due to automatic review settings August 18, 2025 08:52
@coderabbitai
Copy link

coderabbitai bot commented Aug 18, 2025

Walkthrough

The PR introduces a plugin-driven CLI generation pipeline, updates SDK generator APIs and exports, refactors runtime CRUD/dialect handling (including distinct/cursor flow and operation typing), tightens validators to strict objects, adds custom-type→jsonb mapping, adjusts tests accordingly, removes baseUrl from multiple tsconfigs, updates scripts, and bumps package versions.

Changes

Cohort / File(s) Summary
CLI: plugin-driven generation
packages/cli/src/actions/generate.ts, packages/cli/src/index.ts, packages/cli/src/plugins/index.ts, packages/cli/src/plugins/prisma.ts, packages/cli/src/plugins/typescript.ts, packages/cli/test/generate.test.ts, packages/cli/test/plugins/*.test.ts, packages/cli/tsconfig.json
Replace built-in generation with plugin pipeline; remove --save-prisma-schema; add core prisma/typescript plugins; dynamic plugin resolution; tests updated/added; remove baseUrl tsconfig.
SDK: generator API and exports
packages/sdk/src/cli-plugin.ts, packages/sdk/src/ts-schema-generator.ts, packages/sdk/src/index.ts, packages/sdk/src/generator.ts (deleted), packages/runtime/test/scripts/generate.ts, packages/testtools/src/schema.ts
Introduce CliPlugin/CliGeneratorContext; TsSchemaGenerator.generate now takes Model + outputDir; remove old generator types; re-export cli-plugin and add ModelUtils; update consumers to loadDocument and pass model.
Runtime: CRUD refactor and dialect consolidation
packages/runtime/src/client/client-impl.ts, packages/runtime/src/client/crud-types.ts, packages/runtime/src/client/crud/operations/{base.ts,find.ts}, packages/runtime/src/client/crud/dialects/{base.ts,postgresql.ts,sqlite.ts}, packages/runtime/src/client/crud/validator.ts, packages/runtime/src/client/plugin.ts, packages/runtime/src/client/helpers/schema-db-pusher.ts, packages/runtime/test/client-api/find.test.ts, packages/runtime/test/schemas/typing/typecheck.ts
Add CoreCrudOperation/dual operation propagation; capability-aware FindArgs (distinct); centralize where/orderBy/skip/take/cursor/distinct via buildFilterSortTake; tighten validators with strictObject; map custom types to jsonb; adjust plugin export; backend-conditional tests and distinct/cursor scenarios; minor typing removal in tests.
Config: baseUrl removals in tsconfig
packages/{cli,common-helpers,create-zenstack,dialects/sql.js,ide/vscode,language,sdk,tanstack-query,testtools,zod}/tsconfig.json
Remove compilerOptions.baseUrl from multiple packages’ tsconfigs.
Version bumps and catalog
package.json, pnpm-workspace.yaml, packages/*/package.json, samples/blog/package.json, tests/e2e/package.json
Bump versions to 3.0.0-alpha.27; update Prisma catalog to ^6.14.0; add @types/node to catalog; various package version updates; sample scripts switch to “zen ...” commands.
VSCode launch config
.vscode/launch.json
Change debug CLI to rely on cwd for schema (args simplified; set cwd to samples/blog/zenstack).
Docs/TODO
TODO.md
Update task statuses; add new items (e.g., provider-scoped attributes, tsdown migration, MySQL under PostgreSQL).
Language minor typing
packages/language/src/validators/datamodel-validator.ts
Use non-null assertion in isDelegateModel check (no runtime change).
Zod: stricter select schema
packages/zod/src/index.ts
Use z.strictObject for select schema construction.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • merge dev to main #96 — Also changes CLI generation flow and SDK generator types; overlaps with CLI pluginization and generator signature updates.
  • feat: select distinct #10 — Refactors DISTINCT handling and query stack; touches crud-types and dialect bases similar to this PR’s distinct/capability changes.
  • merge dev to main (v3.0.0-alpha.26) #177 — Adjusts runtime operation type surface (AllCrudOperation); related to introducing CoreCrudOperation and dual-operation propagation.

Poem

Hops on the keys, my whiskers twitch—
Plugins bloom; the build goes swish.
Queries align, distinct in tow,
Cursors glide where records flow.
Strict little carrots (zods) in rows,
A jsonb burrow where custom grows.
Thump-thump—release! Onward it goes. 🥕✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@claude
Copy link

claude bot commented Aug 18, 2025

Claude finished @ymc9's task —— View job


PR Review: dev → main

I'll conduct a comprehensive review of this merge PR covering code quality, potential issues, performance, security, and test coverage.

Review Tasks:

  • Analyze major architectural changes (CLI plugin system)
  • Review runtime client improvements and custom types support
  • Check security implications of new code
  • Evaluate test coverage for new features
  • Review code quality and best practices
  • Assess performance considerations
  • Provide constructive feedback and recommendations

🔍 Comprehensive Analysis

This PR merges significant architectural improvements and bug fixes from the dev branch to main, representing the 3.0.0-alpha.27 release.

🏗️ Major Features Added

1. CLI Plugin System (#181)

New Architecture:

  • Pluggable code generation system with clean CliPlugin interface
  • Support for both built-in (@core/) and external plugins
  • Dynamic plugin loading with relative path and npm package support

Code Quality Assessment:
Good practices:

  • Well-defined interface in packages/sdk/src/cli-plugin.ts:32-47
  • Proper error handling for plugin loading failures
  • Clean separation between core and external plugins

⚠️ Security Concerns:

  • HIGH: Dynamic imports without sanitization at packages/cli/src/actions/generate.ts:78

    cliPlugin = (await import(moduleSpec)).default as CliPlugin;

    This allows arbitrary code execution if an attacker can control the plugin path

  • MEDIUM: No validation that loaded modules conform to CliPlugin interface before execution

  • LOW: Plugin execution errors could leak sensitive information

Recommendations:

  1. Implement plugin signature validation
  2. Sanitize plugin paths and restrict to trusted locations
  3. Add plugin sandboxing or runtime validation
  4. Consider plugin manifests or allow-lists for production

2. Custom Types in DB Pusher (#184)

Enhancement:

  • Added support for custom types stored as JSONB in database
  • Proper type checking with isCustomType() method
  • Cross-dialect support (PostgreSQL JSONB, SQLite JSON text)

Implementation Review:
Strengths:

  • Consistent JSONB mapping across dialects in packages/runtime/src/client/helpers/schema-db-pusher.ts:191-193
  • Type-safe validation integration in validator
  • Proper handling in result transformation

Potential Issues:

  • No schema versioning for custom type evolution
  • Limited query capabilities on JSONB fields
  • No validation of JSONB structure at runtime

🐛 Bug Fixes

SQLite Distinct Restriction (#180)

  • Properly disallows distinct operations for SQLite to prevent runtime errors
  • Good defensive programming practice

Input Validation Improvements (#178, #179)

  • Consistent use of z.strictObject for better validation
  • Fixed relation selection validation issues

🔒 Security Analysis

High Priority Issues:

  1. Plugin System Vulnerability: Arbitrary code execution via dynamic imports
  2. Input Sanitization: Limited validation of plugin options in packages/cli/src/actions/generate.ts:124-138

Medium Priority:

  • No rate limiting on plugin execution
  • Error messages may expose internal paths

Low Priority:

  • Plugin failure handling could be more graceful

📊 Test Coverage Assessment

Well Covered:
✅ Custom plugin integration tests: packages/cli/test/plugins/custom-plugin.test.ts
✅ Prisma plugin tests: packages/cli/test/plugins/prisma-plugin.test.ts
✅ Comprehensive find operation tests: packages/runtime/test/client-api/find.test.ts

Coverage Gaps:
❌ No security tests for plugin loading
❌ Limited error boundary testing for plugin failures
❌ No performance tests for large schema generation

⚡ Performance Considerations

Positive:

  • Plugin loading is cached appropriately
  • Schema validation caching maintained
  • Efficient topological sorting for model dependencies

Areas for Optimization:

  • Multiple plugin execution could be parallelized
  • Large custom type serialization could impact performance
  • No lazy loading of plugin modules

🏆 Code Quality Assessment

Architecture: ⭐⭐⭐⭐⭐ (5/5)

  • Clean plugin architecture with proper interfaces
  • Good separation of concerns
  • Extensible design

Type Safety: ⭐⭐⭐⭐⭐ (5/5)

  • Strong TypeScript usage throughout
  • Proper generic constraints
  • Runtime type validation

Error Handling: ⭐⭐⭐⭐ (4/5)

  • Good error context in most places
  • Some areas could provide better error messages
  • Plugin errors could be more descriptive

Security: ⭐⭐ (2/5)

  • Major vulnerability in plugin system
  • Needs immediate attention before production use

📝 Recommendations

Critical (Fix Before Merge)

  1. Secure Plugin Loading: Add path sanitization and signature validation
  2. Plugin Interface Validation: Verify loaded modules match expected interface

High Priority

  1. Add security tests for plugin system
  2. Implement plugin execution timeouts
  3. Add plugin manifest validation

Medium Priority

  1. Add performance benchmarks for code generation
  2. Improve error messages with better context
  3. Consider plugin execution parallelization

Low Priority

  1. Add schema versioning documentation
  2. Expand test coverage for edge cases
  3. Add plugin development documentation

✅ Approval Recommendation

⚠️ CONDITIONAL APPROVAL - This PR contains excellent architectural improvements but has a critical security vulnerability in the plugin system that must be addressed before merging to main.

The custom types feature and input validation improvements are well-implemented and ready for production. The plugin architecture is well-designed but needs security hardening.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR merges the dev branch to main, bumping the version from 3.0.0-alpha.26 to 3.0.0-alpha.27 and implementing a new CLI plugin architecture. The key changes include:

  • Refactored CLI generate command to use a plugin-based architecture with core TypeScript and Prisma plugins
  • Updated multiple TypeScript configuration files to remove "baseUrl" compiler option
  • Enhanced runtime query builder with improved distinct support and cursor pagination
  • Fixed type annotations and improved API design for better type safety

Reviewed Changes

Copilot reviewed 58 out of 59 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/e2e/package.json Version bump to 3.0.0-alpha.27
samples/blog/package.json Version bump and updated CLI command names
pnpm-workspace.yaml Updated Prisma version and added @types/node catalog entry
packages/zod/src/index.ts Changed from z.object to z.strictObject for better validation
packages/runtime/src/client/crud/validator.ts Enhanced validation with strictObject usage
packages/runtime/src/client/crud-types.ts Added provider-specific distinct support
packages/cli/src/actions/generate.ts Complete rewrite to implement plugin architecture
packages/sdk/src/ts-schema-generator.ts Simplified API to accept Model directly instead of file paths
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.

@ymc9 ymc9 changed the title merge dev to main merge dev to main (v3.0.0-alpha.27) Aug 18, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (1)
packages/runtime/src/client/client-impl.ts (1)

369-372: Bug: post-processing uses the original args instead of the possibly overridden _args

Plugins can override args by wrapping proceed. Using the outer args here ignores those overrides and can lead to incorrect result processing (e.g., select/include).

Apply this diff:

-                if (r && postProcess) {
-                    result = resultProcessor.processResult(r, model, args);
-                } else {
+                if (r && postProcess) {
+                    result = resultProcessor.processResult(r, model, _args as any);
+                } else {
🧹 Nitpick comments (32)
packages/runtime/src/client/helpers/schema-db-pusher.ts (2)

191-194: Handle arrays and provider differences for custom types

The early return maps every custom type to plain jsonb, which:

  • bypasses the array handling below (Lines 210–216), so CustomType[] becomes jsonb instead of jsonb[] (PostgreSQL),
  • forces jsonb on non-PostgreSQL providers, which can be an invalid or undesirable column type.

Consider adding array handling and gating by provider:

-        if (this.isCustomType(fieldDef.type)) {
-            return 'jsonb';
-        }
+        if (this.isCustomType(fieldDef.type)) {
+            if (this.schema.provider.type === 'postgresql') {
+                return fieldDef.array ? sql.raw('jsonb[]') : 'jsonb';
+            }
+            // Non-Postgres fallback for custom types. Consider aligning with how `Json` is handled cross-provider.
+            return 'text';
+        }

Notes:

  • If you intend custom types to behave like built-in Json everywhere, mirror whatever cross-provider strategy you want for Json (today it returns jsonb unconditionally). If keeping jsonb universally is intentional, at least fix the array case: return fieldDef.array ? sql.raw('jsonb[]') : 'jsonb';
  • To avoid accidental shadowing of built-ins by custom types named like Int, String, etc., ensure isCustomType excludes known built-ins or move custom-type resolution after built-in detection.

218-221: Make custom-type detection O(1) and more robust

Object.values(...).some(...) is O(n) per call and repeats for every field. If typeDefs is keyed by type name (likely), you can simplify and speed this up. Also be explicit about nullability.

-    private isCustomType(type: string) {
-        return this.schema.typeDefs && Object.values(this.schema.typeDefs).some((def) => def.name === type);
-    }
+    private isCustomType(type: string) {
+        const defs = this.schema.typeDefs;
+        // If `typeDefs` is keyed by name, prefer O(1) lookup:
+        return !!defs && Object.prototype.hasOwnProperty.call(defs, type);
+    }

If typeDefs is NOT keyed by name, keep current semantics but consider precomputing a Set once for O(1) lookups:

// Outside the selected lines: example approach
private readonly customTypeNames = new Set<string>(
    Object.values(this.schema.typeDefs ?? {}).map((d) => d.name),
);

private isCustomType(type: string) {
    return this.customTypeNames.has(type);
}

Optionally also add a guard to exclude known built-ins to prevent shadowing.

packages/zod/src/index.ts (1)

10-10: Optional: align the SelectSchema type with strictness

SelectSchema currently aliases ZodObject<...> without specifying the unknown-keys mode (defaults to 'strip'), while the runtime schema here is strict. Consider updating the type to reflect strictness for better type fidelity.

If acceptable, adjust packages/zod/src/types.ts:

// before
export type SelectSchema<Schema extends SchemaDef, Model extends GetModels<Schema>> = ZodObject<{
    [Key in ScalarFields<Schema, Model>]: MapScalarType<Schema, Model, Key>;
}>;

// after (note: second generic param 'strict')
export type SelectSchema<Schema extends SchemaDef, Model extends GetModels<Schema>> = ZodObject<{
    [Key in ScalarFields<Schema, Model>]: MapScalarType<Schema, Model, Key>;
}, 'strict'>;
pnpm-workspace.yaml (1)

8-8: Keep Prisma CLI and Client in lockstep via catalog

Recommend adding '@prisma/client' to the catalog with the same range as 'prisma' to avoid drift across packages and samples. This prevents subtle mismatches between generator and client.

Apply this diff:

 catalog:
     kysely: ^0.27.6
     zod: ^4.0.0
     prisma: ^6.14.0
+    '@prisma/client': ^6.14.0
     langium: 3.5.0
     langium-cli: 3.5.0
     ts-pattern: ^5.7.1
     typescript: ^5.0.0
     '@types/node': ^20.17.24
     tmp: ^0.2.3
     '@types/tmp': ^0.2.6
packages/language/src/validators/datamodel-validator.ts (1)

443-447: Tiny readability refactor: capture the base model ref once

To avoid repeating property access and non-null assertions, grab a local baseModelRef after the invariant and reuse it.

Apply this diff:

-        invariant(model.baseModel.ref, 'baseModel must be resolved');
-
-        // check if the base model is a delegate model
-        if (!isDelegateModel(model.baseModel.ref!)) {
+        const baseModelRef = model.baseModel.ref;
+        invariant(baseModelRef, 'baseModel must be resolved');
+
+        // check if the base model is a delegate model
+        if (!isDelegateModel(baseModelRef)) {
             accept('error', `Model ${model.baseModel.$refText} cannot be extended because it's not a delegate model`, {
                 node: model,
                 property: 'baseModel',
             });
             return;
         }
.vscode/launch.json (1)

13-15: Relying on CWD for schema discovery matches the new plugin-driven flow

Switching to args: ["generate"] with cwd pointing at the sample app’s zenstack folder is consistent with the updated CLI behavior.

Optionally add verbose logging to ease debugging:

-            "args": ["generate"],
+            "args": ["generate", "--verbose"],
TODO.md (5)

12-15: Fix markdown list indentation (MD007) for nested bullets under "CLI".

Current indentation uses 4 and 8 spaces; markdownlint expects 2 and 4 for 2nd/3rd levels. Adjust as below.

-    - [x] plugin mechanism
-    - [x] built-in plugins
-        - [x] ts
-        - [x] prisma
+  - [x] plugin mechanism
+  - [x] built-in plugins
+    - [x] ts
+    - [x] prisma

17-19: Normalize indentation for "ZModel" sub-items (MD007).

Use 2 spaces for the second-level list items under ZModel.

-    - [x] Import
+  - [x] Import
-    - [ ] Datasource provider-scoped attributes
+  - [ ] Datasource provider-scoped attributes

88-89: Indentation of "Migrate to tsdown" under ORM/Misc is too deep.

Expected 3rd-level indent (4 spaces) per markdownlint; reduce by 4 spaces.

-        - [ ] Migrate to tsdown
+    - [ ] Migrate to tsdown

Note: Line 89 starts the next section; ensure its indentation is consistent with its parent as well.


91-91: Indentation for "Post-mutation hooks..." under "Plugin" should be 2 spaces.

-    - [x] Post-mutation hooks should be called after transaction is committed
+  - [x] Post-mutation hooks should be called after transaction is committed

109-109: Indentation for "MySQL" under "Databases" (MD007).

Make it a second-level bullet (2 spaces).

-    - [ ] MySQL
+  - [ ] MySQL
packages/runtime/src/client/crud-types.ts (1)

633-644: Provider-gated distinct in FindArgs is correct; consider aligning validator behavior.

Typing only exposes distinct for PostgreSQL via ProviderSupportsDistinct, which matches dialect capabilities. However, the runtime validator currently always accepts distinct (see validator.ts Line 212-218). Consider gating in the validator too for consistency and earlier feedback to users.

If helpful, I can patch validator.ts to only allow distinct when schema.provider.type === 'postgresql'.

packages/runtime/src/client/crud/validator.ts (2)

212-218: Gate distinct by provider for consistency with types and dialects.

Types only allow distinct for PostgreSQL, but the validator accepts it unconditionally. Aligning validator prevents confusing "accepted but ignored" args on providers that don't support it.

-            fields['distinct'] = this.makeDistinctSchema(model).optional();
+            if (this.schema.provider.type === 'postgresql') {
+                fields['distinct'] = this.makeDistinctSchema(model).optional();
+            }

630-656: Gate relation distinct for to-many includes by provider as well.

Same rationale as above: only PostgreSQL supports DISTINCT ON(field). Keep runtime validation aligned with type-level gating.

-            ...(fieldDef.array
-                ? {
-                      // to-many relations can be ordered, skipped, taken, and cursor-located
-                      orderBy: z.lazy(() => this.makeOrderBySchema(fieldDef.type, true, false)).optional(),
-                      skip: this.makeSkipSchema().optional(),
-                      take: this.makeTakeSchema().optional(),
-                      cursor: this.makeCursorSchema(fieldDef.type).optional(),
-                      distinct: this.makeDistinctSchema(fieldDef.type).optional(),
-                  }
-                : {}),
+            ...(fieldDef.array
+                ? {
+                      // to-many relations can be ordered, skipped, taken, and cursor-located
+                      orderBy: z.lazy(() => this.makeOrderBySchema(fieldDef.type, true, false)).optional(),
+                      skip: this.makeSkipSchema().optional(),
+                      take: this.makeTakeSchema().optional(),
+                      cursor: this.makeCursorSchema(fieldDef.type).optional(),
+                      ...(this.schema.provider.type === 'postgresql'
+                          ? { distinct: this.makeDistinctSchema(fieldDef.type).optional() }
+                          : {}),
+                  }
+                : {}),
packages/runtime/test/scripts/generate.ts (1)

24-29: Surface load warnings and improve error readability

Minor DX improvement: print warnings from loadDocument and format errors line-by-line. This helps when schema validations fail during CI.

Apply:

-    const result = await loadDocument(schemaPath, pluginModelFiles);
-    if (!result.success) {
-        throw new Error(`Failed to load schema from ${schemaPath}: ${result.errors}`);
-    }
-    await generator.generate(result.model, outputDir);
+    const result = await loadDocument(schemaPath, pluginModelFiles);
+    if (result.warnings?.length) {
+        console.warn(`Warnings while loading schema from ${schemaPath}:\n${result.warnings.join('\n')}`);
+    }
+    if (!result.success) {
+        throw new Error(`Failed to load schema from ${schemaPath}:\n${result.errors.join('\n')}`);
+    }
+    await generator.generate(result.model, outputDir);
packages/cli/src/plugins/prisma.ts (2)

9-17: Always ensure the output directory exists (even for the default path)

Right now mkdirSync runs only when pluginOptions.output is provided. If defaultOutputPath hasn’t been created by the caller, writeFileSync will fail. Make directory creation unconditional.

Apply this diff:

         let outDir = defaultOutputPath;
         if (typeof pluginOptions['output'] === 'string') {
             outDir = path.resolve(defaultOutputPath, pluginOptions['output']);
-            if (!fs.existsSync(outDir)) {
-                fs.mkdirSync(outDir, { recursive: true });
-            }
         }
+        if (!fs.existsSync(outDir)) {
+            fs.mkdirSync(outDir, { recursive: true });
+        }
         const prismaSchema = await new PrismaSchemaGenerator(model).generate();
         fs.writeFileSync(path.join(outDir, 'schema.prisma'), prismaSchema);

10-11: Optional: Disallow absolute output paths to keep generation confined under the default output root

If you want to prevent plugins from writing outside defaultOutputPath, guard against absolute output values.

Example:

-        if (typeof pluginOptions['output'] === 'string') {
+        if (typeof pluginOptions['output'] === 'string') {
+            if (path.isAbsolute(pluginOptions['output'])) {
+                throw new Error(`'output' must be a relative path`);
+            }
             outDir = path.resolve(defaultOutputPath, pluginOptions['output']);
packages/cli/test/plugins/prisma-plugin.test.ts (1)

17-30: Optional: Add a quick content sanity check

To catch accidental empty writes, consider a minimal file-size or non-empty content check in addition to existsSync. This isn’t critical but can surface regressions earlier.

Example:

const tsPath = path.join(workDir, 'generated-schema/schema.ts');
expect(fs.existsSync(tsPath)).toBe(true);
expect(fs.readFileSync(tsPath, 'utf8').length).toBeGreaterThan(0);
packages/cli/src/plugins/typescript.ts (2)

10-18: Unconditionally ensure the output directory exists

Similar to the Prisma plugin, mkdirSync currently happens only when pluginOptions.output is set. TsSchemaGenerator will usually create directories, but making it explicit here improves robustness and consistency.

Apply this diff:

         let outDir = defaultOutputPath;
         if (typeof pluginOptions['output'] === 'string') {
             outDir = path.resolve(defaultOutputPath, pluginOptions['output']);
-            if (!fs.existsSync(outDir)) {
-                fs.mkdirSync(outDir, { recursive: true });
-            }
         }
+        if (!fs.existsSync(outDir)) {
+            fs.mkdirSync(outDir, { recursive: true });
+        }
         await new TsSchemaGenerator().generate(model, outDir);

9-16: Optional: Avoid duplication by extracting a shared helper for output resolution

Both plugins share identical outDir resolution/creation logic. Consider a small helper under packages/cli/src/plugins/_utils.ts to reduce drift.

Example helper (new file: packages/cli/src/plugins/_utils.ts):

import fs from 'node:fs';
import path from 'node:path';

export function resolveOutDir(defaultOutputPath: string, pluginOptions: Record<string, unknown>) {
    let outDir = defaultOutputPath;
    const output = pluginOptions['output'];
    if (typeof output === 'string') {
        if (path.isAbsolute(output)) {
            throw new Error(`'output' must be a relative path`);
        }
        outDir = path.resolve(defaultOutputPath, output);
    }
    fs.mkdirSync(outDir, { recursive: true });
    return outDir;
}

Then in each plugin:

import { resolveOutDir } from './_utils';
const outDir = resolveOutDir(defaultOutputPath, pluginOptions);
packages/testtools/src/schema.ts (2)

45-48: Improve error message formatting and include all diagnostics

When loadDocument fails, result.errors is an array; interpolating it directly results in a comma-joined string. Joining with newlines improves readability.

Also consider surfacing result.warnings (if any) to aid debugging.

Apply this diff for clearer error output:

-    if (!result.success) {
-        throw new Error(`Failed to load schema from ${zmodelPath}: ${result.errors}`);
-    }
+    if (!result.success) {
+        throw new Error(`Failed to load schema from ${zmodelPath}:\n${result.errors.join('\n')}`);
+    }

Optionally, log warnings after a successful load:

  • if (result.warnings.length) console.warn(Schema warnings:\n${result.warnings.join('\n')});

85-87: Mirror the improved error formatting here as well

Same suggestion as above for better readability and diagnostics when loading from a schema path fails.

Apply this diff:

-    if (!result.success) {
-        throw new Error(`Failed to load schema from ${schemaPath}: ${result.errors}`);
-    }
+    if (!result.success) {
+        throw new Error(`Failed to load schema from ${schemaPath}:\n${result.errors.join('\n')}`);
+    }
packages/runtime/src/client/crud/operations/find.ts (1)

6-24: Guard against unsupported operations at runtime

The handler is specific to find operations but the signature accepts any CoreCrudOperation. Adding a lightweight runtime guard prevents accidental dispatch (and surfaces clearer errors).

Apply this diff to add a defensive check:

 export class FindOperationHandler<Schema extends SchemaDef> extends BaseOperationHandler<Schema> {
-    async handle(operation: CoreCrudOperation, args: unknown, validateArgs = true): Promise<unknown> {
+    async handle(operation: CoreCrudOperation, args: unknown, validateArgs = true): Promise<unknown> {
+        if (operation !== 'findMany' && operation !== 'findUnique' && operation !== 'findFirst') {
+            throw new Error(`FindOperationHandler received unsupported operation: ${operation}`);
+        }
         // normalize args to strip `undefined` fields
         const normalizedArgs = this.normalizeArgs(args);
packages/cli/test/plugins/custom-plugin.test.ts (1)

1-50: End-to-end plugin generation test reads well; add a quick content assertion

The test exercises the custom plugin path correctly (TS compilation, CLI invocation, output path resolution). Consider also asserting the file content to catch false positives where a file exists but is empty or wrong.

Apply this diff:

         runCli('generate', workDir);
         expect(fs.existsSync(path.join(workDir, 'custom-output/foo.txt'))).toBe(true);
+        expect(fs.readFileSync(path.join(workDir, 'custom-output/foo.txt'), 'utf8')).toBe('from my plugin');
packages/runtime/test/client-api/find.test.ts (1)

244-249: Distinct behavior tests: good coverage, minor type nit

  • Creating duplicate post titles for dedup validation is spot-on.
  • Gating SQLite with an explicit rejection check is appropriate given provider capabilities.
  • Include/select combinations are exercised well.

Nit: using as any for distinct is understandable for cross-backend typing, but consider narrowing types in tests to avoid any where possible.

Also applies to: 264-271, 279-305

packages/runtime/src/client/crud/dialects/base.ts (2)

62-108: Centralized where/skip-take/orderBy/distinct/cursor composition is a good consolidation; consider default order when using cursor

This method greatly improves consistency. One functional suggestion: when a cursor is present and no explicit orderBy is provided, applying the default order ensures deterministic paging.

Apply this diff to enable default ordering when a cursor is used:

         result = this.buildOrderBy(
             result,
             model,
             model,
             args.orderBy,
-            skip !== undefined || take !== undefined,
+            skip !== undefined || take !== undefined || args.cursor !== undefined,
             negateOrderBy,
         );

Optional: For DISTINCT ON determinism, consider prepending distinct fields to the ORDER BY when supported (leftmost), or at least documenting the behavior when orderBy is omitted.


169-208: Cursor filter: make subquery scalar and ignore non-scalar orderings

Two robustness tweaks:

  • Ensure the subquery used for comparisons returns a single row (defensive, even if cursor is unique).
  • Filter orderBy entries to scalar sorts only ('asc' | 'desc'), falling back to the default if none remain. This prevents accidental inclusion of relation/aggregate sorts that the lexical cursor comparison can’t handle.

Apply this diff:

-        const _orderBy = orderBy ?? makeDefaultOrderBy(this.schema, model);
-
-        const orderByItems = ensureArray(_orderBy).flatMap((obj) => Object.entries<SortOrder>(obj));
+        const _orderBy = orderBy ?? makeDefaultOrderBy(this.schema, model);
+
+        let orderByItems = ensureArray(_orderBy)
+            .flatMap((obj) => Object.entries(obj))
+            .filter(([, v]) => v === 'asc' || v === 'desc') as [string, SortOrder][];
+        if (orderByItems.length === 0) {
+            orderByItems = ensureArray(makeDefaultOrderBy(this.schema, model))
+                .flatMap((obj) => Object.entries(obj)) as [string, SortOrder][];
+        }
@@
-                        eb.selectFrom(model).select(`${model}.${field}`).where(cursorFilter),
+                        eb.selectFrom(model).select(`${model}.${field}`).where(cursorFilter).limit(1),

Note: If you intend to support cursor with aggregate/relation orderings later, this area will need a different strategy (e.g., projecting computed order keys in a CTE and comparing tuples).

packages/cli/src/actions/generate.ts (5)

78-81: Improve dynamic import error message

Interpolating the error object yields “[object Object]”. Prefer the message for clarity.

Apply this diff:

-            } catch (error) {
-                throw new CliError(`Failed to load plugin ${provider}: ${error}`);
-            }
+            } catch (error) {
+                throw new CliError(
+                    `Failed to load plugin ${provider}: ${
+                        error instanceof Error ? error.message : String(error)
+                    }`,
+                );
+            }

95-99: Prefer CLI-friendly error for missing generate()

Use CliError so the top-level CLI can render a consistent, user-friendly message.

Apply this diff:

-        invariant(
-            typeof cliPlugin.generate === 'function',
-            `Plugin ${cliPlugin.name} does not have a generate function`,
-        );
+        if (typeof cliPlugin.generate !== 'function') {
+            throw new CliError(`Plugin ${cliPlugin.name} does not have a generate function`);
+        }

87-92: Nit: reverse() on a single-element array is redundant

defaultPlugins contains one element; reverse() is a no-op. Consider removing for clarity.

No code change required; optional cleanup:

-    const defaultPlugins = [corePlugins['typescript']].reverse();
+    const defaultPlugins = [corePlugins['typescript']];

101-109: Consider honoring --silent for spinners and logs

Even when options.silent is true, ora spinners (and console.warn in getPluginOptions) still emit output. Consider threading a silent flag into runPlugins to disable spinners and warnings.

If you choose to implement, minimal shape:

  • Add a silent param to runPlugins and gate spinner creation with it.
  • Gate console.warns in option parsing behind the same flag (or route through a logger).

Happy to provide a concrete patch if you want to wire silent through.


29-33: Add tests to verify plugin failures abort the generate command

There’s currently no test ensuring that a plugin error prevents the generate command from printing a success message. Without this, a regression could allow “Generation completed successfully” to appear even when a plugin fails. Please add a test (e.g. packages/cli/test/generate.test.ts) that:

  • Installs or mocks a plugin whose generate hook throws an error (or omits the generate function).
  • Invokes the CLI via runCli('generate', workDir) (or equivalent).
  • Asserts that it rejects or exits with a non-zero code, not logging the success message.

This will guard against silent failures in plugin execution.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6262b76 and 2ad8857.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (58)
  • .vscode/launch.json (1 hunks)
  • TODO.md (3 hunks)
  • package.json (2 hunks)
  • packages/cli/package.json (1 hunks)
  • packages/cli/src/actions/generate.ts (3 hunks)
  • packages/cli/src/index.ts (0 hunks)
  • packages/cli/src/plugins/index.ts (1 hunks)
  • packages/cli/src/plugins/prisma.ts (1 hunks)
  • packages/cli/src/plugins/typescript.ts (1 hunks)
  • packages/cli/test/generate.test.ts (0 hunks)
  • packages/cli/test/plugins/custom-plugin.test.ts (1 hunks)
  • packages/cli/test/plugins/prisma-plugin.test.ts (1 hunks)
  • packages/cli/tsconfig.json (0 hunks)
  • packages/common-helpers/package.json (1 hunks)
  • packages/common-helpers/tsconfig.json (0 hunks)
  • packages/create-zenstack/package.json (1 hunks)
  • packages/create-zenstack/tsconfig.json (0 hunks)
  • packages/dialects/sql.js/package.json (1 hunks)
  • packages/dialects/sql.js/tsconfig.json (0 hunks)
  • packages/eslint-config/package.json (1 hunks)
  • packages/ide/vscode/package.json (1 hunks)
  • packages/ide/vscode/tsconfig.json (0 hunks)
  • packages/language/package.json (1 hunks)
  • packages/language/src/validators/datamodel-validator.ts (1 hunks)
  • packages/language/tsconfig.json (0 hunks)
  • packages/runtime/package.json (1 hunks)
  • packages/runtime/src/client/client-impl.ts (20 hunks)
  • packages/runtime/src/client/crud-types.ts (2 hunks)
  • packages/runtime/src/client/crud/dialects/base.ts (4 hunks)
  • packages/runtime/src/client/crud/dialects/postgresql.ts (1 hunks)
  • packages/runtime/src/client/crud/dialects/sqlite.ts (1 hunks)
  • packages/runtime/src/client/crud/operations/base.ts (5 hunks)
  • packages/runtime/src/client/crud/operations/find.ts (1 hunks)
  • packages/runtime/src/client/crud/validator.ts (11 hunks)
  • packages/runtime/src/client/helpers/schema-db-pusher.ts (2 hunks)
  • packages/runtime/src/client/plugin.ts (1 hunks)
  • packages/runtime/test/client-api/find.test.ts (7 hunks)
  • packages/runtime/test/schemas/typing/typecheck.ts (0 hunks)
  • packages/runtime/test/scripts/generate.ts (2 hunks)
  • packages/sdk/package.json (1 hunks)
  • packages/sdk/src/cli-plugin.ts (1 hunks)
  • packages/sdk/src/generator.ts (0 hunks)
  • packages/sdk/src/index.ts (1 hunks)
  • packages/sdk/src/ts-schema-generator.ts (1 hunks)
  • packages/sdk/tsconfig.json (0 hunks)
  • packages/tanstack-query/package.json (1 hunks)
  • packages/tanstack-query/tsconfig.json (0 hunks)
  • packages/testtools/package.json (2 hunks)
  • packages/testtools/src/schema.ts (3 hunks)
  • packages/testtools/tsconfig.json (0 hunks)
  • packages/typescript-config/package.json (1 hunks)
  • packages/vitest-config/package.json (1 hunks)
  • packages/zod/package.json (1 hunks)
  • packages/zod/src/index.ts (1 hunks)
  • packages/zod/tsconfig.json (0 hunks)
  • pnpm-workspace.yaml (1 hunks)
  • samples/blog/package.json (1 hunks)
  • tests/e2e/package.json (1 hunks)
💤 Files with no reviewable changes (14)
  • packages/sdk/tsconfig.json
  • packages/cli/test/generate.test.ts
  • packages/dialects/sql.js/tsconfig.json
  • packages/testtools/tsconfig.json
  • packages/tanstack-query/tsconfig.json
  • packages/runtime/test/schemas/typing/typecheck.ts
  • packages/language/tsconfig.json
  • packages/ide/vscode/tsconfig.json
  • packages/zod/tsconfig.json
  • packages/cli/tsconfig.json
  • packages/sdk/src/generator.ts
  • packages/create-zenstack/tsconfig.json
  • packages/common-helpers/tsconfig.json
  • packages/cli/src/index.ts
🧰 Additional context used
📓 Path-based instructions (2)
{packages,samples,tests}/**

📄 CodeRabbit Inference Engine (CLAUDE.md)

Packages are located in packages/, samples/, and tests/

Files:

  • packages/tanstack-query/package.json
  • packages/vitest-config/package.json
  • packages/create-zenstack/package.json
  • packages/common-helpers/package.json
  • packages/sdk/package.json
  • packages/cli/src/plugins/prisma.ts
  • packages/zod/package.json
  • tests/e2e/package.json
  • packages/sdk/src/cli-plugin.ts
  • packages/language/package.json
  • packages/cli/src/plugins/index.ts
  • packages/eslint-config/package.json
  • samples/blog/package.json
  • packages/language/src/validators/datamodel-validator.ts
  • packages/cli/package.json
  • packages/typescript-config/package.json
  • packages/cli/test/plugins/prisma-plugin.test.ts
  • packages/ide/vscode/package.json
  • packages/cli/src/plugins/typescript.ts
  • packages/runtime/src/client/crud/dialects/postgresql.ts
  • packages/testtools/package.json
  • packages/dialects/sql.js/package.json
  • packages/runtime/src/client/crud/operations/find.ts
  • packages/zod/src/index.ts
  • packages/runtime/package.json
  • packages/sdk/src/ts-schema-generator.ts
  • packages/runtime/src/client/plugin.ts
  • packages/runtime/src/client/crud/dialects/sqlite.ts
  • packages/runtime/src/client/crud-types.ts
  • packages/runtime/test/client-api/find.test.ts
  • packages/runtime/test/scripts/generate.ts
  • packages/sdk/src/index.ts
  • packages/cli/test/plugins/custom-plugin.test.ts
  • packages/runtime/src/client/crud/validator.ts
  • packages/testtools/src/schema.ts
  • packages/runtime/src/client/crud/dialects/base.ts
  • packages/runtime/src/client/helpers/schema-db-pusher.ts
  • packages/cli/src/actions/generate.ts
  • packages/runtime/src/client/client-impl.ts
  • packages/runtime/src/client/crud/operations/base.ts
tests/e2e/**

📄 CodeRabbit Inference Engine (CLAUDE.md)

E2E tests are in tests/e2e/ directory

Files:

  • tests/e2e/package.json
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
PR: zenstackhq/zenstack-v3#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T08:43:33.161Z
Learning: Database migrations still use Prisma CLI under the hood
📚 Learning: 2025-08-04T08:43:33.161Z
Learnt from: CR
PR: zenstackhq/zenstack-v3#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T08:43:33.161Z
Learning: `zenstack generate` compiles ZModel to TypeScript schema (`schema.ts`)

Applied to files:

  • packages/cli/src/plugins/prisma.ts
  • .vscode/launch.json
  • packages/language/package.json
  • packages/cli/src/plugins/typescript.ts
  • packages/sdk/src/ts-schema-generator.ts
  • packages/runtime/test/scripts/generate.ts
  • packages/sdk/src/index.ts
  • packages/testtools/src/schema.ts
  • packages/cli/src/actions/generate.ts
📚 Learning: 2025-08-04T08:43:33.161Z
Learnt from: CR
PR: zenstackhq/zenstack-v3#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T08:43:33.161Z
Learning: No runtime dependency on prisma/client

Applied to files:

  • pnpm-workspace.yaml
📚 Learning: 2025-08-04T08:43:33.161Z
Learnt from: CR
PR: zenstackhq/zenstack-v3#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T08:43:33.161Z
Learning: ZModel schema (`schema.zmodel`) defines database structure and policies

Applied to files:

  • packages/runtime/src/client/crud/validator.ts
📚 Learning: 2025-08-04T08:43:33.161Z
Learnt from: CR
PR: zenstackhq/zenstack-v3#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T08:43:33.161Z
Learning: Schema-first approach with ZModel DSL extension of Prisma schema language

Applied to files:

  • packages/runtime/src/client/crud/validator.ts
📚 Learning: 2025-08-04T08:43:33.161Z
Learnt from: CR
PR: zenstackhq/zenstack-v3#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T08:43:33.161Z
Learning: Applies to **/schema.zmodel : Always run `zenstack generate` after modifying ZModel schemas

Applied to files:

  • packages/testtools/src/schema.ts
🧬 Code Graph Analysis (18)
packages/cli/src/plugins/prisma.ts (2)
packages/sdk/src/cli-plugin.ts (1)
  • CliPlugin (32-47)
packages/sdk/src/index.ts (1)
  • PrismaSchemaGenerator (3-3)
packages/sdk/src/cli-plugin.ts (1)
packages/runtime/src/utils/type-utils.ts (1)
  • MaybePromise (70-70)
packages/language/src/validators/datamodel-validator.ts (2)
packages/language/src/utils.ts (1)
  • isDelegateModel (153-155)
packages/sdk/src/model-utils.ts (1)
  • isDelegateModel (69-71)
packages/cli/test/plugins/prisma-plugin.test.ts (1)
packages/cli/test/utils.ts (2)
  • createProject (12-18)
  • runCli (20-23)
packages/cli/src/plugins/typescript.ts (2)
packages/sdk/src/cli-plugin.ts (1)
  • CliPlugin (32-47)
packages/sdk/src/ts-schema-generator.ts (1)
  • TsSchemaGenerator (54-1388)
packages/runtime/src/client/crud/operations/find.ts (3)
packages/sdk/src/schema/schema.ts (1)
  • SchemaDef (10-18)
packages/runtime/src/client/plugin.ts (1)
  • CoreCrudOperation (49-49)
packages/runtime/src/client/crud/operations/base.ts (1)
  • CoreCrudOperation (51-66)
packages/zod/src/index.ts (2)
packages/runtime/src/client/crud/operations/base.ts (1)
  • schema (87-89)
packages/zod/src/types.ts (1)
  • SelectSchema (4-6)
packages/sdk/src/ts-schema-generator.ts (2)
packages/language/src/generated/ast.ts (2)
  • Model (559-563)
  • Model (565-565)
packages/sdk/src/prisma/prisma-builder.ts (1)
  • Model (115-164)
packages/runtime/src/client/crud-types.ts (2)
packages/sdk/src/schema/schema.ts (3)
  • SchemaDef (10-18)
  • GetModels (108-108)
  • NonRelationFields (190-194)
packages/runtime/src/utils/type-utils.ts (1)
  • OrArray (48-48)
packages/runtime/test/client-api/find.test.ts (4)
packages/runtime/test/client-api/client-specs.ts (1)
  • createClientSpecs (6-42)
packages/runtime/test/client-api/utils.ts (1)
  • createUser (6-21)
packages/runtime/src/client/crud/dialects/postgresql.ts (1)
  • provider (26-28)
packages/runtime/src/client/crud/dialects/sqlite.ts (1)
  • provider (26-28)
packages/runtime/test/scripts/generate.ts (1)
packages/language/src/index.ts (1)
  • loadDocument (21-132)
packages/cli/test/plugins/custom-plugin.test.ts (2)
packages/cli/test/utils.ts (2)
  • createProject (12-18)
  • runCli (20-23)
packages/cli/src/utils/exec-utils.ts (1)
  • execSync (6-15)
packages/runtime/src/client/crud/validator.ts (2)
packages/runtime/test/schemas/basic/schema.ts (1)
  • schema (9-284)
samples/blog/zenstack/schema.ts (1)
  • schema (9-228)
packages/testtools/src/schema.ts (2)
packages/language/src/index.ts (1)
  • loadDocument (21-132)
packages/sdk/src/ts-schema-generator.ts (1)
  • TsSchemaGenerator (54-1388)
packages/runtime/src/client/crud/dialects/base.ts (4)
packages/sdk/src/schema/schema.ts (1)
  • GetModels (108-108)
packages/runtime/src/client/crud-types.ts (1)
  • SortOrder (370-370)
packages/runtime/src/client/query-utils.ts (2)
  • ensureArray (274-280)
  • makeDefaultOrderBy (217-220)
packages/runtime/src/client/errors.ts (1)
  • QueryError (13-17)
packages/cli/src/actions/generate.ts (5)
packages/language/src/generated/ast.ts (5)
  • Model (559-563)
  • Model (565-565)
  • isPlugin (628-630)
  • Plugin (619-624)
  • Plugin (626-626)
packages/sdk/src/cli-plugin.ts (1)
  • CliPlugin (32-47)
packages/cli/src/cli-error.ts (1)
  • CliError (4-4)
packages/common-helpers/src/tiny-invariant.ts (1)
  • invariant (4-14)
packages/language/src/utils.ts (2)
  • getLiteral (269-283)
  • getLiteralArray (308-316)
packages/runtime/src/client/client-impl.ts (2)
packages/runtime/src/client/plugin.ts (1)
  • CoreCrudOperation (49-49)
packages/runtime/src/client/crud/operations/base.ts (2)
  • CoreCrudOperation (51-66)
  • AllCrudOperation (68-68)
packages/runtime/src/client/crud/operations/base.ts (1)
packages/runtime/src/client/plugin.ts (1)
  • CoreCrudOperation (49-49)
🪛 markdownlint-cli2 (0.17.2)
TODO.md

12-12: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


13-13: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


14-14: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


15-15: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


17-17: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


18-18: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


19-19: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


88-88: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


89-89: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


91-91: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


109-109: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: claude-review
  • GitHub Check: build-test (20.x)
🔇 Additional comments (63)
packages/zod/src/index.ts (2)

10-10: Review strict select schemas for extra-key handling

Switching from z.stripObject to z.strictObject means extra keys will now trigger parse errors. Please review all makeSelectSchema call sites and ensure callers only pass exact shapes—or update them and add tests to assert that unexpected fields are rejected.

Affected locations in packages/runtime/src/client/crud/validator.ts:

  • Line 208: fields['select'] = this.makeSelectSchema(model).optional();
  • Line 587: private makeSelectSchema(model: string) { … }
  • Line 637: select: z.lazy(() => this.makeSelectSchema(fieldDef.type)).optional(),
  • Line 749: select: this.makeSelectSchema(model).optional(),
  • Line 763: select: this.makeSelectSchema(model).optional(),
  • Line 1012: select: this.makeSelectSchema(model).optional(),
  • Line 1030: select: this.makeSelectSchema(model).optional(),
  • Line 1041: select: this.makeSelectSchema(model).optional(),
  • Line 1151: select: this.makeSelectSchema(model).optional(),

– Confirm each call site either already enforces exact keys or update the implementation/callers.
– Add or adjust unit tests to verify that passing extra properties results in parse failures.


10-10: Verify Zod version for z.strictObject support

Our scan found that zod is declared only as a peerDependency with a non-specific version (catalog:) in these packages:

  • packages/runtime/package.json (peerDependencies): zod@catalog:
  • packages/zod/package.json (peerDependencies): zod@catalog:

Because it’s unclear which Zod release will be installed at runtime—and z.strictObject was only added in Zod ≥ 3.21.4—please either:

  1. Confirm your end-to-end dependency resolution installs Zod ≥ 3.21.4 (in which case z.strictObject(...) is safe),
  2. Or switch to the well-supported .strict() pattern to guarantee compatibility:
-    return z.strictObject(mapFields(schema, model)) as SelectSchema<Schema, typeof model>;
+    return z.object(mapFields(schema, model)).strict() as SelectSchema<Schema, typeof model>;
pnpm-workspace.yaml (1)

13-13: Node types are included by default—no tsconfig changes needed

All 14 tsconfig*.json files omit a "types" array, which means TypeScript automatically picks up every installed @types package (including @types/node). No explicit types: ["node"] entries are required.

packages/runtime/package.json (1)

3-3: Version bump looks good

Consistent with the repo-wide alpha.27 bump. No export or dependency changes here.

packages/common-helpers/package.json (1)

3-3: Approved: package version bump only

No surface/API changes; aligns with the workspace version.

packages/create-zenstack/package.json (1)

3-3: Approved: version bump

No behavioral changes; release metadata updated as expected.

packages/sdk/package.json (1)

3-3: Approved: SDK version bump – re-exports confirmed

Verified that:

  • packages/sdk/src/index.ts contains
    export * from './cli-plugin';
  • packages/sdk/src/cli-plugin.ts defines and exports
    export interface CliPlugin {  }

No further changes needed.

packages/ide/vscode/package.json (1)

4-4: Version bump to 3.0.0-alpha.27 verified and ready to publish

  • No lingering 3.0.0-alpha.26 references found in the repo.
  • engines.vscode remains ^1.63.0 in packages/ide/vscode/package.json.
  • Version (3.0.0-alpha.27) and publish script (pnpm build && vsce publish --no-dependencies --pre-release --follow-symlinks) are correctly configured.
packages/vitest-config/package.json (1)

4-4: Consistent version bump; no functional changes here

Nothing else changed in this package; safe to release with the rest of the monorepo.

packages/cli/package.json (1)

6-6: Version bump verified; update CLI changelog

All package.json versions are now at 3.0.0-alpha.27 and there are no lingering references to 3.0.0-alpha.26.
Please ensure the CLI changelog or release notes document any removed flags or behavior changes.

packages/eslint-config/package.json (1)

3-3: LGTM: version bumped to 3.0.0-alpha.27

No other changes; consistent with the monorepo release.

packages/zod/package.json (1)

3-3: Version bump acknowledged; exports and deps unchanged

Looks good alongside the rest of the release.

packages/typescript-config/package.json (1)

3-3: LGTM: version bump to 3.0.0-alpha.27

Monorepo meta version update looks consistent with the broader release bump.

tests/e2e/package.json (1)

3-3: LGTM: e2e package version bump aligned

Version aligns with the workspace release bump. No issues spotted.

packages/language/package.json (1)

4-4: LGTM: language package version bump

Matches the workspace-wide 3.0.0-alpha.27 release. No functional changes here.

package.json (1)

3-3: LGTM: root version bumped to 3.0.0-alpha.27

Root version aligns with the release train.

packages/dialects/sql.js/package.json (1)

3-3: LGTM: dialect package version bump

Consistent with the monorepo version update. No API changes implied.

packages/language/src/validators/datamodel-validator.ts (1)

446-446: Non-null assertion after invariant is safe here

Using model.baseModel.ref! is sound given the preceding invariant(model.baseModel.ref, ...). This also makes the type explicit at the call site.

packages/tanstack-query/package.json (1)

3-3: All internal packages consistently bumped to 3.0.0-alpha.27
Verified via script – no mismatches found.

packages/testtools/package.json (2)

3-3: Version bump is consistent with the release train

No issues spotted with moving to 3.0.0-alpha.27.


44-48: Node.js types are included by default via @types/node

All tsconfig.json files—including packages/testtools/tsconfig.json—extend the shared @zenstackhq/typescript-config/base.json, which does not specify a compilerOptions.types array. By TypeScript’s defaults, omitting types means all @types/* packages in node_modules are loaded, so your added @types/node will be picked up without needing an explicit "types": ["node"] entry.

No changes required here.

samples/blog/package.json (2)

3-3: Version bump is fine for the sample app

No functional impact beyond aligning with the workspace release.


7-9: Scripts and CLI dependency verified in samples/blog/package.json

All three scripts (generate, db:push, db:migrate) match the expected zen CLI commands, and @zenstackhq/cli is present under devDependencies as workspace:*. No further changes needed.

packages/runtime/src/client/crud-types.ts (2)

616-626: Good separation of concerns: FilterArgs and SortAndTakeArgs.

Breaking the args into composable parts improves readability and reuse across find variants. No issues spotted.


1263-1270: Provider capability checks updated to use provider.type — LGTM.

This matches how validator checks case sensitivity and aligns with SchemaDef.

packages/runtime/src/client/crud/validator.ts (11)

748-753: Strict create args: good tightening without changing shape.

Switching to z.strictObject here is consistent with the overall stricter validation strategy.


762-766: Switch from merge to extend: confirm no behavior drift.

extend avoids potential strictness and overlap pitfalls from merge, but it changes composition semantics for overlapping keys. Looks fine given keys are additive (select, omit), but please confirm there are no cases where base may already contain those keys and relied on merge behavior.

Would you like me to run a quick grep to confirm no callers expect createManyAndReturn/updateManyAndReturn to accept additional keys via merge semantics?


990-995: Strict object for connectOrCreate payload: LGTM.

Tighter schema helps catch stray fields early.


998-1002: Strict object for createMany data: LGTM.

Consistent with strict inputs elsewhere.


1009-1017: Strict update args: LGTM.

Consistent tightening; mutual exclusivity refinements are preserved.


1020-1025: Strict updateMany args: LGTM.

No concerns.


1029-1034: Switch to extend for updateManyAndReturn: same note as createManyAndReturn.

Semantics are sound; just ensure no overlap assumptions existed.

I can scan tests/usages to ensure no reliance on merge behavior here.


1037-1046: Strict upsert args: LGTM.

Matches the rest of the CRUD surface.


1150-1155: Strict delete args: LGTM.

Consistent with strictness push and exclusivity checks.


1188-1199: Count aggregate input strictness: LGTM.

Explicit key set prevents typos from silently passing.


1258-1271: GroupBy strict object and refinements: LGTM.

Rules for by/having/orderBy are sound and now enforced on a strict base.

packages/runtime/src/client/crud/operations/base.ts (3)

51-69: CoreCrudOperation and AllCrudOperation introduction: backward-compat maintained.

Clear separation of core vs. nominal “OrThrow” operations. Looks good.


99-100: Abstract handle signature switched to CoreCrudOperation: LGTM.

Matches the new operation types and plugin export aliasing.


150-152: Unified filter/sort/take via dialect.buildFilterSortTake: nice consolidation.

Reduces duplication and centralizes provider-specific behavior (distinct/cursor/negative take flip). No issues spotted.

If desired, I can cross-check that both sqlite and postgres dialects implement identical semantics for skip/take/order flipping and cursor filtering via a quick code scan.

packages/runtime/src/client/plugin.ts (1)

49-49: Re-exporting CoreCrudOperation as CrudOperation preserves API surface.

This keeps external plugin code stable while aligning internals. LGTM.

packages/runtime/src/client/crud/dialects/sqlite.ts (1)

87-90: buildFilterSortTake usage verified with full cursor/distinct/skip/take coverage

  • buildFilterSortTake is defined on BaseCrudDialect and invoked in both sqlite.ts and postgresql.ts.
  • Runtime tests exercise cursor pagination, skip/take behavior (including negative takes), distinct handling (with SQLite’s unsupported‐distinct error), and SQLite’s offset‐without‐limit workaround via buildSkipTake.

No further changes needed—merging as is.

packages/cli/src/plugins/index.ts (1)

1-2: Re-exporting core plugins from a central index is straightforward and clear

The named exports make plugin discovery simple and align with the new plugin-driven flow.

packages/runtime/src/client/crud/dialects/postgresql.ts (1)

93-95: buildFilterSortTake correctly handles DISTINCT ON and cursor pagination in Postgres

  • In postgresql.ts, supportsDistinctOn is true, so the helper’s if (this.supportsDistinctOn) branch is taken.
  • The base implementation applies buildOrderBy before adding the distinctOn clause and then applies the cursor filter (buildCursorFilter) using the same ordering and caret-negation logic.
  • Existing find tests for PG cover both distinct with skip/take and cursor scenarios and are green.

No changes needed.

packages/sdk/src/ts-schema-generator.ts (1)

55-66: Model-based generate signature applied: all call sites updated

Verified that every instantiation of TsSchemaGenerator now invokes generate(model, outputDir) and no callers rely on the old signature.
Key call-sites updated accordingly:

  • packages/testtools/src/schema.ts (lines 50, 88)
  • packages/runtime/test/scripts/generate.ts (line 28)
  • packages/cli/src/plugins/typescript.ts (line 17)

All internal and external invocations have migrated to the new signature.

packages/cli/src/plugins/prisma.ts (1)

5-19: Solid plugin implementation; aligns with the new CLI plugin contract

The plugin’s shape, status text, and generation flow look correct and consistent with the TypeScript plugin. Using PrismaSchemaGenerator(model).generate() and writing schema.prisma under the resolved outDir is straightforward and clear.

packages/cli/test/plugins/prisma-plugin.test.ts (1)

6-60: Good end-to-end coverage for default and custom outputs

The tests validate both built-in behavior and plugin-specified outputs for TypeScript and Prisma, matching the expected directory structures. Using createProject + runCli mirrors real usage and keeps assertions focused on the contract.

packages/cli/src/plugins/typescript.ts (1)

6-19: Plugin implementation looks consistent and correct

Consistent with the Prisma plugin, resolves output dir, and delegates to TsSchemaGenerator with the expected signature generate(model, outDir). Nice.

packages/sdk/src/cli-plugin.ts (3)

7-27: Context shape is clear and sufficient

schemaFile, model, defaultOutputPath, and pluginOptions cover the needs for current plugins and allow future growth. The doc comments are succinct.


32-46: Interface reads well; keep the signature stable

The CliPlugin contract is minimal and easy to implement. Maintaining generate(context): MaybePromise stable across minor versions will help ecosystem plugins.


2-2: No action needed: ‘langium’ is a declared dependency
The SDK’s package.json lists “langium” under dependencies, so importing MaybePromise as a type won’t break consumers. You can keep the existing import.

Likely an incorrect or invalid review comment.

packages/sdk/src/index.ts (1)

1-7: Root exports are coherent and align with the new plugin architecture

Re-exporting the CLI plugin contract and generators (Prisma/TS) from the SDK root makes consumption by the CLI plugins straightforward. Adding the ModelUtils namespace is a nice touch.

packages/testtools/src/schema.ts (3)

1-1: Import alignment with new model-based flow looks correct

Importing loadDocument from @zenstackhq/language matches the new, model-first generation flow.


51-51: Updated generator API usage is correct

Passing result.model into TsSchemaGenerator.generate(model, workDir) matches the updated SDK API.


89-89: Consistent generator invocation

Using result.model for in-place generation is consistent and correct.

packages/runtime/src/client/crud/operations/find.ts (1)

3-3: Rename to CoreCrudOperation is consistent with the base changes

Import change looks aligned with the refactor introducing CoreCrudOperation.

packages/runtime/test/client-api/find.test.ts (3)

10-10: Provider-aware test matrix is a good evolution

Including provider in the spec tuple improves clarity and enables backend-specific gating below.


674-695: PostgreSQL-only nested distinct scenarios are well isolated

These assertions directly validate distinct interplay with nested selects/includes and ordering. Clear separation by provider avoids flaky behavior on SQLite.


913-925: Cursor within include: solid addition

This ensures cursor semantics are respected for nested relations, complementing top-level cursor tests.

packages/runtime/src/client/crud/dialects/base.ts (3)

24-25: Importing ensureArray is appropriate

The helper is used for normalizing potentially non-array inputs (e.g., distinct, orderBy).


95-102: Provider-gated distinct-on with clear error message

Throwing a QueryError for providers without DISTINCT ON support is clear and matches tests. No change requested.


815-815: Type tightening on aggregate order entries

Using Object.entries<SortOrder>(value) improves type clarity when iterating aggregate order keys.

packages/runtime/src/client/client-impl.ts (2)

354-356: LGTM: clear split between core and nominal operations

Separating the operation executed by handlers (CoreCrudOperation) from the nominal operation seen by plugins (AllCrudOperation) makes plugin semantics precise.


385-392: LGTM: onQuery now receives the nominal operation

Passing nominalOperation to plugins aligns with developer expectations (e.g., findFirst vs findFirstOrThrow) and matches the new typing model.

@ymc9 ymc9 enabled auto-merge August 18, 2025 09:18
@ymc9 ymc9 added this pull request to the merge queue Aug 18, 2025
Merged via the queue into main with commit c5660c5 Aug 18, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants